﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NFit.Core.Interfaces;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.IO;

namespace NFit.Core.Reports
{
    /// <summary>
    /// Report writer for assembly
    /// </summary>
    public class AssemblyReport : IReportWriter
    {
        /// <summary>
        /// Root namespace
        /// </summary>
        public string RootNamespace { get; set; }

        /// <summary>
        /// Root namespace
        /// </summary>
        public string GenerationPath { get; set; }

        /// <summary>
        /// The custom writer for the fixtures
        /// </summary>
        public ISpecificationWriter Writer
        {
            get { return null; }
            set {  }
        }

        /// <summary>
        /// Make the report for the associated summary
        /// </summary>
        /// <param name="summary"></param>
        public void Report(ExecutionSummary summary)
        {
            CodeCompileUnit unit = new CodeCompileUnit();

            unit.ReferencedAssemblies.Add("NFit.Core.dll");
            unit.ReferencedAssemblies.Add("System.dll");

            foreach(var spec in summary.RunnedSpecs)
            {
                Write(unit, spec);
            }
            if (!Directory.Exists(GenerationPath))
                Directory.CreateDirectory(GenerationPath);

            string code = GenerateCode(unit);
            using(StreamWriter writer = new StreamWriter(Path.Combine(GenerationPath, "code.cs")))
            {
                writer.Write(code);
            }
            Compile(code);
        }

        private string GenerateCode(CodeCompileUnit unit)
        {
            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            StringBuilder generatedCode = new StringBuilder();
            StringWriter codeWriter = new StringWriter(generatedCode);

            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BracingStyle = "C";
            //Keep the braces on the line following the statement or 
            //declaration that they are associated with
            codeProvider.GenerateCodeFromCompileUnit(unit, codeWriter, options);

            return generatedCode.ToString();
        }

        private string Compile(string code)
        {
            CSharpCodeProvider codeProvider = new CSharpCodeProvider();

            CompilerParameters parameters = new CompilerParameters();
            parameters.ReferencedAssemblies.Add("NFit.Core.dll");
            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.GenerateInMemory = false;
            parameters.OutputAssembly = Path.Combine(GenerationPath, RootNamespace + ".dll");

            CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);
            if (results.Errors.HasErrors)
            {
                string errorMessage = string.Empty;
                errorMessage = results.Errors.Count.ToString() + " Errors:";
                for (int x = 0; x < results.Errors.Count; x++)
                {
                    errorMessage = errorMessage + "\r\nLine: " +
                        results.Errors[x].Line.ToString() + " - " + results.Errors[x].ErrorText;
                }
                return errorMessage;
            }
            return results.PathToAssembly; 

        }

        private void Write(CodeCompileUnit unit, SpecificationDefinition spec)
        {
            CodeNamespace customEntityRoot = new CodeNamespace(SpecNamespace(spec));//Create a namespace            

            customEntityRoot.Imports.Add(new CodeNamespaceImport("System"));//Add references
            customEntityRoot.Imports.Add(new CodeNamespaceImport("NFit.Core"));//Add references

            CodeTypeDeclaration specClass = new CodeTypeDeclaration(SpecClassName(spec));//Create class
            customEntityRoot.Types.Add(specClass);//Add the class to namespace defined above

            specClass.BaseTypes.Add(new CodeTypeReference(typeof(SpecificationDefinition)));

            CodeConstructor specClassConstructor = new CodeConstructor();//Create constructor
            specClassConstructor.Attributes = MemberAttributes.Public;
            specClass.Members.Add(specClassConstructor);//Add constructor to class

            CodeMemberMethod derivedMethod = new CodeMemberMethod();
            derivedMethod.Attributes = MemberAttributes.Public | MemberAttributes.Override;
             //Make this method an override of base class's method
            derivedMethod.Comments.Add(new CodeCommentStatement(new CodeComment("TestComment")));
            derivedMethod.Name = "Initialize";
            derivedMethod.ReturnType = new CodeTypeReference(typeof(void));
            foreach(var fixture in spec)
            {
                WriteFixture(derivedMethod, fixture);
            }
            specClass.Members.Add(derivedMethod);

            foreach (var child in spec.ChildSpecs)
            {
                Write(unit, child);
            }

            unit.Namespaces.Add(customEntityRoot);
        }

        private void WriteFixture(CodeMemberMethod method, FixtureDefinition fixture)
        {
            System.Text.StringBuilder sb = new StringBuilder();
            sb.AppendLine("AddFixture()");

            AddDescription(sb, "WithHeader", fixture.Header);

            foreach(var row in fixture)
            {
                sb.AppendLine(".WithRow(" + RowString(row) + ")");  
            }

            AddDescription(sb, "WithFooter", fixture.Footer);

            sb.Append(";");
            method.Statements.Add(new CodeSnippetStatement(sb.ToString()));

        }

        private void AddDescription(StringBuilder sb, string descname, string desc)
        {
            if (!string.IsNullOrEmpty(desc))
            {
                sb.AppendLine("." + descname + "(\"" + desc + "\")");
            }
        }

        private string RowString(FixtureRow row)
        {
            StringBuilder sb = new StringBuilder();
            foreach(var cell in row)
            {
                if (sb.Length > 0) sb.Append(",");
                sb.Append("\"" + cell.Text + "\"");
            }
            return sb.ToString();
        }

        private string SpecNamespace(SpecificationDefinition spec)
        {            
            string classes = string.Empty;
            SpecificationDefinition curspec = spec;
            while (curspec.ParentSpecification != null)
            {
                classes = SpecClassName(curspec.ParentSpecification) + "Suite" + ((classes.Length > 0) ? "." + classes : classes);
                curspec = curspec.ParentSpecification;
            }
            if (!string.IsNullOrEmpty(RootNamespace))
            {
                classes = RootNamespace + ((classes.Length > 0) ? "." + classes : string.Empty);
            }
            return classes;
        }

        private string SpecClassName(SpecificationDefinition spec)
        {
            return spec.Name;
        }
    }
}
